/** file InstrumentPolyphonique.cpp
    author belot.nicolas
*/

#include "InstrumentPolyphonique.h"
#include "SampleFactory.h"
#include "ChangePitch.h"
#include <iostream>
#include <cmath>

namespace libtrckr {

InstrumentPolyphonique::InstrumentPolyphonique():Instrument(){
	_name=string();
	//_tune=string();
	//_partition=queue<INote>();
//	_samples=map<string, INote>();
}

InstrumentPolyphonique::InstrumentPolyphonique(string Name):Instrument(),_name(Name){
	//_tune=string();
	//_partition=queue<INote>();

//	_samples=map<string, INote>();
}

InstrumentPolyphonique::InstrumentPolyphonique(const InstrumentPolyphonique & im):Instrument(im),
	_name(im._name), 
	_tune(im._tune){
	//_partition(im._partition),
	//in fact we need to copy samples and attack...
	//will do it later.
}


InstrumentPolyphonique::~InstrumentPolyphonique(){
	for(uint i=0;i< nb_string;i++){
		delete _partitions[i];
		delete current[i];
	}

	/*for(uint i=0;i< nb_attack;i++){
		delete attack[i];
	}
	free(attack);
	free(elapsed);
	free(duration);
	free(mvolume);
	free(current);
	free(_partitions);*/
}

string InstrumentPolyphonique::getName() const {
	return _name;
}

DynArray<string> InstrumentPolyphonique::getTuning() const{
	DynArray<string> ret(1,_tune);
	return ret;
}

DynArray<string> InstrumentPolyphonique::getAttacks() const {
	return attack;
	/*DynArray<string> ret(nb_attack,string());
	for(uint i=0;i<nb_attack;i++){
		ret[i]= new string(*attack[i]);
	}
	ret[nb_attack]=0;
	return ret;*/
	
}

void InstrumentPolyphonique::setName( string Name){
	_name=Name;
}

void InstrumentPolyphonique::setTuning( DynArray<string> tune){//only one tuning...
	_tune=tune[0];
	nb_string=10;
	
	_partitions = DynArray<queue<INote>*>(nb_string,NULL);
	elapsed = DynArray<uint>(nb_string);
	duration = DynArray<uint>(nb_string);
	current = DynArray<Sample*>(nb_string,NULL);
	mvolume = DynArray<double>(nb_string);
	ratio = DynArray<double>(nb_string,1.);
	conv = DynArray<Converter>(nb_string, Converter(1.0,1024));
	effects = DynArray<DynArray<PlayingEffect*>*>(nb_string,NULL);
	for(uint i=0;i<nb_string;i++){
		_partitions[i] = new queue<INote>();
		
	}
}

void InstrumentPolyphonique::setNote( string name, string file,float speed,short panning,short att,short where){
	INote in;
	in._note = Note(name,Duration::NOIRE,false);
	in.filename=file;
	in.speed=speed;
	if(speed != 1.){
		std::cerr << "Speed set to another value that ZERO. Actually we don't support dynamic modification of sample speed" << std::endl;
	}
	in._panning=panning;
	in.attack=att;
	if(att >= 0 && att < nb_attack  && _samples[att]!=0)
		_samples[att]->insert(pair<string, INote>(name, in) );
}

void InstrumentPolyphonique::setAttack(string **att){
	int cnt=0;
	while(att[cnt]!=0){
		cnt++;
	}
	attack=DynArray<string>(cnt,string());
	_samples=DynArray<map<string,INote>*>( cnt,NULL );
	for(uint i=0;i<cnt;i++){
		attack[i]= *att[i];
		_samples[i] = new map<string,INote>();
	}
	nb_attack=cnt;

}

void InstrumentPolyphonique::setAttack(DynArray<string> att){
	attack=att;
	_samples=DynArray<map<string,INote>*>(att.size(),NULL);
	for(uint i=0;i<att.size();i++){
		_samples[i] = new map<string,INote>();
	}
	nb_attack=att.size();
}


Buffer InstrumentPolyphonique::play( uint size){
	Buffer ret = Buffer(size);
	Buffer tmp = Buffer(size);
	for(uint i=0;i<nb_string;i++){
		tmp.flush();
		if(elapsed[i] > size){
			if(current[i]!=0)
				naturalEffects(tmp,*current[i],effects[i]);	
			//tmp << *current[i];
			//else we return a blank buffer...
			elapsed[i]-=size;
			if(current[i]->isEOF()){
				current[i]=0;
			}
		}else{
			Buffer tret(elapsed[i]);
			if(current[i]!=0)
				naturalEffects(tret,*current[i],effects[i]);
				//tmp.append(current[i]->read(elapsed[i]));
			if(_partitions[i]->size()>0){
				INote t = _partitions[i]->front();
				_partitions[i]->pop();
				if(t._note.name()=="blank" && !ring){
					current[i]=0;
					tmp.append(tret);
					for(uint j=duration[i]-elapsed[i];j<size;j++){
						tmp[j]=.0;
					}
				}else if(_samples[t.attack]->find(t._note.name())!=_samples[t.attack]->end()){
					//we should destroy current[i] ?
					current[i] = SampleFactory::newSample( (*_samples[t.attack])[t._note.name()].filename.c_str());
					mvolume[i]=t._volume;
					ratio[i]=(*_samples[t.attack])[t._note.name()].speed;
					if(ratio[i]!=1.0){
						PlayingEffect *pitch = new ChangePitch();
						DynArray<double> conf(1,ratio[i]);
						pitch->Tune(conf);
						int psize = (*_samples[t.attack])[t._note.name()].playing->size()+1;
						effects[i] = new DynArray<PlayingEffect*>(psize , NULL);
						(*effects[i])[0]=pitch;
						for(uint j=0;j<(*_samples[t.attack])[t._note.name()].playing->size();j++){
							(*effects[i])[j+1]=  (*(*_samples[t.attack])[t._note.name()].playing)[j];
						}
					}else{
						effects[i]=(*_samples[t.attack])[t._note.name()].playing;
					}
					Buffer tret2(size-elapsed[i]);
					naturalEffects(tret2,*current[i],effects[i]);
					tmp+=tret+tret2;
					//tmp << *current[i];
					if(current[i]->isEOF()){
						current[i]=0;
					}
				}else{
					//no such note...should die ?
				}
				int ltmp = size - (duration[i] - elapsed[i]);
				duration[i] = t._note.sampleLength();
				elapsed[i] = duration[i] - ltmp;
			}else{
				elapsed[i]=0;
			}
		}
		for(uint j=0;j<size;j++){
			ret[j]+=(tmp[j]*mvolume[i]);
		}
	}
	return ret;
}
 
Buffer& InstrumentPolyphonique::play( Buffer& b){
	uint size = b.capacity();
	b.flush();
	Buffer tmp = Buffer(size);
	for(uint i=0;i<nb_string;i++){
		tmp.flush();
		if(elapsed[i] > size){
			if(current[i]!=0)
				naturalEffects(tmp, *current[i],effects[i]);
				//tmp << *current[i];
			//else we return a blank buffer...
			elapsed[i]-=size;
		}else{
			Buffer tret(elapsed[i]);
			if(current[i]!=0)
				naturalEffects(tret,*current[i],effects[i]);
				//tmp.append(current[i]->read(elapsed[i]));
			if(_partitions[i]->size()>0){
				INote t = _partitions[i]->front();
				_partitions[i]->pop();
				if(i==0){
					temps -= t._note.getLength();
				}
				if(t._note.name()=="blank"){
					current[i]=0;
					tmp.append(tret);
					for(uint j=duration[i]-elapsed[i];j<size;j++){
						tmp[j]=.0;
					}
				}else if(_samples[t.attack]->find(t._note.name())!=_samples[t.attack]->end()){
					//we should destroy current[i] ?
					current[i] = SampleFactory::newSample( (*_samples[t.attack])[t._note.name()].filename.c_str());
					mvolume[i]=t._volume;
					ratio[i]=(*_samples[t.attack])[t._note.name()].speed;
					if(ratio[i]!=1.0){
						PlayingEffect *pitch = new ChangePitch();
						DynArray<double> conf(1,ratio[i]);
						pitch->Tune(conf);
						int psize = (*_samples[t.attack])[t._note.name()].playing->size()+1;
						effects[i] = new DynArray<PlayingEffect*>(psize , NULL);
						(*effects[i])[0]=pitch;
						for(uint j=0;j<(*_samples[t.attack])[t._note.name()].playing->size();j++){
							(*effects[i])[j+1]=  (*(*_samples[t.attack])[t._note.name()].playing)[j];
						}
					}else{
						//effects[i]=(*_samples[t.attack])[t._note.name()].playing;
					}
					Buffer tret2(size-elapsed[i]);
					naturalEffects(tret2,*current[i],effects[i]);
					tmp+=tret+tret2;
					//tmp << *current[i];
				}else{
					//no such note...should die ?
				}
				int ltmp = size - (duration[i] - elapsed[i]);
				duration[i] = t._note.sampleLength();
				elapsed[i] = duration[i] - ltmp;
			}else{
				elapsed[i]=0;
			}
		}
		for(uint j=0;j<size;j++){
			b[j]+=(tmp[j]*mvolume[i]);
		}
	}
	return b;
}

void InstrumentPolyphonique::addNote(string name, Duration::note_duration_t dura, bool point , uint volume,short att,short where,DynArray<PlayingEffect*>* eff=0){
	INote in ;
	in._note = Note(name, dura,point);
	in._volume=pow(10.0, (0.0-volume/100.0)/20.0);
	in.attack=att;
	in.playing=eff;
	if(where >= 0 && where < nb_string){
		if(where==0){
			temps+=in._note.getLength();
		}
		_partitions[where]->push(in);
	}else if(where==nb_string){
		_partitions[where]=new queue<INote>();
		elapsed[where]=elapsed[0];
		duration[where]=duration[0];
		current[where]=0;
		ratio[where]=1.0;
		conv[where]=Converter(1., 1024);
		uint i =0;
		Duration::note_duration_t dure[]={Duration::RONDE,Duration::BLANCHE,Duration::NOIRE,Duration::CROCHE, Duration::DBLECROCHE,Duration::TRPLECROCHE,Duration::QUADPLECROCHE};
		nb_string++;
		Duration d(dure[i]);
		double rest = temps;
		string silence("blank");
		while(rest > in._note.getLength()){
			if(rest > d.getLength()){
				INote n;
				n._note = Note(silence,d,false);
				n._volume = 0;
				n.attack=0;
				_partitions[where]->push(n);
			}else{
				i++;
				d = Duration(dure[i]);
			}
		}
		_partitions[where]->push(in);
	}
}

void InstrumentPolyphonique::addNote(Note n, uint volume,short att,short where,DynArray<PlayingEffect*>*  eff=0){
	INote in;
	in._note=n;
	in._volume=pow(10.0, (0.0-volume/100.0)/20.0);
	in.attack=att;
	in.playing=eff;
	if(where>=0 && where < nb_string){
		if(where==0){
			temps+=n.getLength();
		}
		_partitions[where]->push(in);
	}else if(where==nb_string){
		_partitions[where]=new queue<INote>();
		elapsed[where]=elapsed[0];
		duration[where]=duration[0];
		current[where]=0;
		ratio[where]=1.0;
		conv[where]=Converter(1., 1024);

		uint i =0;
		Duration::note_duration_t dure[]={Duration::RONDE,Duration::BLANCHE,Duration::NOIRE,Duration::CROCHE, Duration::DBLECROCHE,Duration::TRPLECROCHE,Duration::QUADPLECROCHE};
		nb_string++;
		Duration d(dure[i]);
		double rest = temps;
		string silence("blank");
		while(rest > in._note.getLength()){
			if(rest > d.getLength()){
				INote n2;
				n2._note = Note(silence,d,false);
				n2._volume = 0;
				n2.attack=0;
				_partitions[where]->push(n2);
			}else{
				i++;
				d = Duration(dure[i]);
			}
		}
		_partitions[where]->push(in);
	}
}

bool InstrumentPolyphonique::end() const{
	bool partend=true;
	for(uint i=0;i<nb_string;i++){
		if(_partitions[i]->size()!=0 && elapsed[i]!=0)
			partend=false;
	}
	return partend;
}
void InstrumentPolyphonique::letRing(bool ringing){
	ring=ringing;
}
}

